/*
 * Created on Feb 11, 2005
 * Created by dfhuynh
 */
package edu.mit.simile.longwell.schema.impl;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.log4j.Logger;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.NsIterator;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.vocabulary.RDF;

import edu.mit.simile.longwell.Profile;
import edu.mit.simile.longwell.ProfileListenerBase;
import edu.mit.simile.longwell.schema.IStructuredClass;
import edu.mit.simile.longwell.schema.IStructuredProperty;
import edu.mit.simile.longwell.schema.IStructuredSchema;

/**
 * @author dfhuynh
 */
public class SchemaRegistry extends ProfileListenerBase {
	final static private Logger s_logger = Logger.getLogger(SchemaRegistry.class);
	
	final protected Profile	m_profile;
	final protected File	m_dir;
	protected boolean		m_initialized;
	
	static protected File		s_registryDir;
	static protected boolean 	s_initialized;
	
	static public void startup(File registryDir) {
		s_registryDir = registryDir;
	}
	
	static public void shutdown() {
		if (s_initialized) {
			saveRegistryToDir();
		}
	}
	
	public SchemaRegistry(Profile profile, File dir) {
		m_profile = profile;
		m_dir = dir;
	}
	
	public void dispose() {
		if (m_initialized) {
			saveProfileSpecificDataToDir();
			
			StructuredSchema.dispose(m_profile);
			StructuredClass.dispose(m_profile);
			StructuredProperty.dispose(m_profile);
			
			getSignalFile().delete();
		}
	}
	
	public Set guessSchemasOfResource(Resource resource) {
		internalInitialize();
		return StructuredSchema.guessStructuredSchemas(m_profile, resource.getURI());
	}
	
	public Set getStructuredSchemas() {
		internalInitialize();
		return StructuredSchema.getStructuredSchemas(m_profile);
	}

	public IStructuredSchema getStructuredSchema(String uri) {
		internalInitialize();
		return StructuredSchema.getStructuredSchema(m_profile, uri);
	}
	
	public IStructuredClass getStructuredClass(Resource resource) {
		internalInitialize();
		return StructuredClass.getStructuredClass(m_profile, resource, false);
	}
	
	public IStructuredProperty getStructuredProperty(Property property) {
		internalInitialize();
		return StructuredProperty.getStructuredProperty(m_profile, property, false);
	}
	
	public IStructuredClass getStructuredClassForObject(Resource object) {
		internalInitialize();
		
		NodeIterator ni = m_profile.getModel().listObjectsOfProperty(
				object, RDF.type);
		while (ni.hasNext()) {
			RDFNode node = ni.nextNode();
			if (node instanceof Resource) {
				return getStructuredClass((Resource) node);
			}
		}
		return null;
	}
	
	static protected void staticInitialize() {
		if (s_initialized) {
			return;
		}
		
		if (s_registryDir.exists()) {
			try {
				loadRegistryFromDir();
			} catch (Exception e) {
				s_logger.error("Failed to load schema registry from disk. You should shutdown and delete the schema registry directory.");
			}
		} else {
			s_registryDir.mkdirs();
		}
		
		s_initialized = true;
	}
	
	static protected void loadRegistryFromDir() {
		StructuredSchema.load(new File(s_registryDir, "common-schemas.bin"));
		
		StructuredClass.load(new File(s_registryDir, "common-classes.bin"));
		
		StructuredProperty.load(new File(s_registryDir, "common-properties.bin"));
	}
	
	static protected void saveRegistryToDir() {
		StructuredSchema.save(new File(s_registryDir, "common-schemas.bin"));
		
		StructuredClass.save(new File(s_registryDir, "common-classes.bin"));
		
		StructuredProperty.save(new File(s_registryDir, "common-properties.bin"));
	}
	
	protected void internalInitialize() {
		if (m_initialized) {
			return;
		}
		
		staticInitialize();
		
		if (!m_dir.exists() || getSignalFile().exists()) {
			m_dir.mkdirs();
			
			buildRegistryFromModel();
			saveProfileSpecificDataToDir();
		} else {
			loadProfileSpecificDataFromDir();
		}
		
		try {
			getSignalFile().createNewFile();
		} catch (IOException e) {
			s_logger.warn(e);
		}
		
		m_initialized = true;
	}
	
	protected File getSignalFile() {
		return new File(m_dir, "signal");
	}
	
	protected void buildRegistryFromModel() {
		internalOnAfterAdd(m_profile.getModel());
	}
	
	protected void loadProfileSpecificDataFromDir() {
		StructuredSchema.load(new File(m_dir, "schemas.bin"), m_profile);
		
		StructuredClass.load(new File(m_dir, "classes.bin"), m_profile);
		
		StructuredProperty.load(new File(m_dir, "properties.bin"), m_profile);
	}
	
	protected void saveProfileSpecificDataToDir() {
		StructuredSchema.save(new File(m_dir, "schemas.bin"), m_profile);
		
		StructuredClass.save(new File(m_dir, "classes.bin"), m_profile);
		
		StructuredProperty.save(new File(m_dir, "properties.bin"), m_profile);
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.ProfileListenerBase#onAfterAdd(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onAfterAdd(Model model) {
		super.onAfterAdd(model);
		
		internalInitialize();
	
		internalOnAfterAdd(model);
	}
	
	protected void internalOnAfterAdd(Model model) {
		long now = System.currentTimeMillis();
		
		s_logger.debug("Started learning schema model");
		
		NsIterator nsi = model.listNameSpaces();
		while (nsi.hasNext()) {
			StructuredSchema sSchema =
				StructuredSchema.getStructuredSchema(m_profile, nsi.nextNs());
		}
		
		Set classes = new HashSet();
		Set properties = new HashSet();
		
		StmtIterator si = model.listStatements(null, RDF.type, (RDFNode) null);
		while (si.hasNext()) {
			classes.add((Resource) si.nextStatement().getObject()); 
		}
		
		s_logger.debug("Retrieved classes in schemas");
				
		Iterator i = classes.iterator();
		while (i.hasNext()) {
			StructuredClass sClass = 
				StructuredClass.getStructuredClass(
						m_profile, (Resource) i.next(), false);
			
			properties.addAll(sClass.onDataAdded(model));
			
			Iterator j = StructuredSchema.guessStructuredSchemas(
				m_profile, sClass.getURI()).iterator();
			while (j.hasNext()) {
				StructuredSchema sSchema = (StructuredSchema) j.next();
				sSchema.onNewClass(sClass);
			}
		}
		
		s_logger.debug("Learned structured classes");
		
		i = properties.iterator();
		while (i.hasNext()) {
			String propertyURI = (String) i.next();
			
			StructuredProperty sProperty =
				StructuredProperty.getStructuredProperty(
					m_profile, m_profile.getModel().createProperty(propertyURI), false);
			
			sProperty.onDataAdded(model);
		}
		
		s_logger.debug("Learned structured properties");
		
		s_logger.debug(
			"Learned schemas in " + (System.currentTimeMillis() - now) / 1000 + " seconds");
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.ProfileListenerBase#onAfterRemove(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onAfterRemove(Model model) {
		super.onAfterRemove(model);
		
		internalInitialize();
		
		internalOnAfterRemove(model);
	}
	
	protected void internalOnAfterRemove(Model model) {
		Set classes = new HashSet();
		
		ResIterator ri = model.listSubjectsWithProperty(RDF.type);
		while (ri.hasNext()) {
			NodeIterator ni = model.listObjectsOfProperty(
				ri.nextResource(), RDF.type);
			
			while (ni.hasNext()) {
				classes.add(
					StructuredClass.getStructuredClass(
						m_profile, (Resource) ni.nextNode(), false));
			}
		}
		
		Iterator i = classes.iterator();
		while (i.hasNext()) {
			StructuredClass sClass = (StructuredClass) i.next();
			
			if (sClass != null && sClass.onDataRemoved(model)) {
				Iterator j = StructuredSchema.guessStructuredSchemas(
					m_profile, sClass.getURI()).iterator();
				
				while (j.hasNext()) {
					StructuredSchema sSchema = (StructuredSchema) j.next();
					sSchema.onClassChanges(sClass);
				}
			}
		}
	}
	
	static public void printSchemas() {
		staticInitialize();
		
		Iterator i = StructuredSchema.s_uriToRecord.values().iterator();
		while (i.hasNext()) {
			StructuredSchema.Record record =
				(StructuredSchema.Record) i.next();
			
			System.out.println(record.m_uri);
			
			Iterator j = record.m_classes.iterator();
			while (j.hasNext()) {
				String classURI = (String) j.next();
				
				System.out.println("  " + classURI);
				
				StructuredClass.Record classRecord = 
					(StructuredClass.Record) 
						StructuredClass.s_uriToRecord.get(classURI);
				
				Iterator k = classRecord.m_properties.iterator();
				while (k.hasNext()) {
					String propertyURI = (String) k.next();
					
					StructuredProperty.Record propertyRecord = 
						(StructuredProperty.Record) StructuredProperty.s_uriToRecord.get(propertyURI);
					
					printPropertyRecord(propertyRecord);
				}
			}
		}
	}
	
	static void printPropertyRecord(StructuredProperty.Record record) {
		if (record.m_totalCount < 3) {
			return;
		}
		
		long[] 	counts = record.m_typeCounts;
		long	l = 0;
		int		index = -1;
		
		for (int i = 1; i < counts.length; i++) {
			if (counts[i] > l) {
				index = i;
				l = counts[i];
			}
		}
		if (index < 0 && counts[0] > 0) {
			index = 0;
			l = counts[0];
		}
		
		String s = "unknown type";
		final String[] labels = new String[] {
			"literal", 
			"numeric",
			"integer",
			"boolean",
			"dateTime",
			"uri"
		};
		if (index >= 0) {
			s = labels[index] + " " + (l / (float) record.m_totalCount);
		} else {
			s += " " + record.m_totalCount;
		}
		
		System.out.println("    " + record.m_uri + ": " + s); 
	}
}
